package parsing;

import java.io.Reader;
import java.util.LinkedList;
import java.util.List;

import parsing.productions.*;

public class CritterParser implements Parser {

	@Override
	public Program parse(Reader r) throws InvalidSyntaxException{
		Tokenizer t = new Tokenizer(r);
		List<AbstractNode> programChildren = new LinkedList<AbstractNode>();
		while(t.hasNext())
			programChildren.add(parseRule(t));
		return new Program(programChildren);
	}

	/**
	 * Returns a Rule node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Rule
	 * @param t The Tokenizer to parse from
	 * @return The resulting Rule
	 */
	public Rule parseRule(Tokenizer t){
		Condition cond = parseCondition(t);
		Token consumed= t.next();
		if (consumed.getType() != Token.ARR) {
			throw new InvalidSyntaxException("A Rule must connect a Condition to a Command using a \"-->\"");
		}
		Command cmd = parseCommand(t);
		return new Rule(cond, cmd);
	}

	/**
	 * Returns a Command node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Command
	 * @param t The Tokenizer to parse from
	 * @return The resulting Command
	 */
	public Command parseCommand(Tokenizer t){
		List<AbstractNode> cmdChildren = new LinkedList<AbstractNode>();
		while(t.peek().getType() != Token.SEMICOLON){
			if(t.peek().isAction())
				cmdChildren.add(parseAction(t));
			else
				cmdChildren.add(parseUpdate(t));
		}
		t.next();//consume ;
		if (cmdChildren.isEmpty()) {
			throw new InvalidSyntaxException("Command must have at least one Update or Action");
		}
		return new Command(cmdChildren);
	}

	/**
	 * Returns an Update node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Update
	 * @param t The Tokenizer to parse from
	 * @return The resulting Update
	 */
	public Update parseUpdate(Tokenizer t){
		Token consumed= t.next();
		if (consumed.getType() != Token.MEM) {
			throw new InvalidSyntaxException("Updates must start with \"mem[\"");
		}
		consumed= t.next();
		if (consumed.getType() != Token.LBRACKET) {
			throw new InvalidSyntaxException("Updates must start with \"mem[\"");
		}
		Term t1, t2;
		t1 = parseTerm(t);
		consumed= t.next();
		if (consumed.getType() != Token.RBRACKET) {
			throw new InvalidSyntaxException("the Term in \"mem\" must be followed by \"]\"");
		}
		consumed= t.next();
		if (consumed.getType() != Token.ASSIGN) {
			throw new InvalidSyntaxException("Updates must assign a Term to some index of mem");
		}
		t2 = parseTerm(t);
		return new Update(t1, t2);
	}

	/**
	 * Returns a Action node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Action
	 * @param t The Tokenizer to parse from
	 * @return The resulting Action
	 */
	public Action parseAction(Tokenizer t){
		Token tok = t.next();
		if (!tok.isAction()) {
			throw new InvalidSyntaxException("parseRel can only be called if the next Token is a MulOp token");
		}
		Action rtn = null;
		switch (tok.getType()){
		case Token.WAIT: rtn = new Action(Action.WAIT); break;
		case Token.FORWARD: rtn = new Action(Action.FORWARD); break;
		case Token.BACKWARD: rtn = new Action(Action.BACKWARD); break;
		case Token.LEFT: rtn = new Action(Action.LEFT); break;
		case Token.RIGHT: rtn = new Action(Action.RIGHT); break;
		case Token.EAT: rtn = new Action(Action.EAT); break;
		case Token.ATTACK: rtn = new Action(Action.ATTACK); break;
		case Token.GROW: rtn = new Action(Action.GROW); break;
		case Token.BUD: rtn = new Action(Action.BUD); break;
		case Token.MATE: rtn = new Action(Action.MATE); break;
		}
		return rtn;
	}

	/**
	 * Returns a Condition node representing a parse tree from the contents of a Tokenizer
	 * Requires: the next sequence of Tokens in t represents one Condition
	 * @param t The Tokenizer to parse from
	 * @return The resulting Condition
	 */
	public Condition parseCondition(Tokenizer t){
		List<AbstractNode> condChildren = new LinkedList<AbstractNode>();
		condChildren.add(parseConjunction(t));
		while(t.peek().getType() == Token.OR){
			t.next();
			condChildren.add(parseConjunction(t));
		}
		return new Condition(condChildren);
	}

	/**
	 * Returns a Conjunction node representing a parse tree from the contents of a Tokenizer
	 * Requires: the next sequence of Tokens in t represents one Conjunction
	 * @param t The Tokenizer to parse from
	 * @return The resulting Conjunction
	 */
	public Conjunction parseConjunction(Tokenizer t){
		List<AbstractNode> conjChildren = new LinkedList<AbstractNode>();
		conjChildren.add(parseRelation(t));
		while(t.peek().getType() == Token.AND){
			t.next();
			conjChildren.add(parseRelation(t));
		}
		return new Conjunction(conjChildren);
	}

	/**
	 * Returns a Relation node representing a parse tree from the contents of a Tokenizer
	 * Requires: the next sequence of Tokens in t represents one Relation
	 * @param t The Tokenizer to parse from
	 * @return The resulting Relation
	 */
	public Relation parseRelation(Tokenizer t){
		Relation rtn;
		if(t.peek().getType() == Token.LBRACE){
			t.next();
			rtn = new Relation(parseCondition(t));
			Token consumed= t.next();
			if (consumed.getType() != Token.RBRACE) {
				throw new InvalidSyntaxException("if Relation is a Condition, it must end with a \"}\"");
			}
		}
		else{
			rtn = new Relation(parseTerm(t), parseRel(t), parseTerm(t));
		}
		return rtn;
	}

	/**
	 * Returns a Rel node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Rel
	 * @param t The Tokenizer to parse from
	 * @return The resulting Rel
	 */
	public Rel parseRel(Tokenizer t){
		Token tok = t.next();
		if (!tok.isRel()) {
			throw new InvalidSyntaxException("parseRel can only be called if the next Token is a Rel token");
		}
		Rel rtn = null;
		switch (tok.getType()){
		case Token.LT: rtn = new Rel(Rel.LT); break;
		case Token.LE: rtn = new Rel(Rel.LE); break;
		case Token.EQ: rtn = new Rel(Rel.EQ); break;
		case Token.GE: rtn = new Rel(Rel.GE); break;
		case Token.GT: rtn = new Rel(Rel.GT); break;
		case Token.NE: rtn = new Rel(Rel.NE); break;
		}
		return rtn;
	}

	/**
	 * Returns a Term node representing a parse tree from the contents of a Tokenizer
	 * Requires: the next sequence of Tokens in t represents one Term
	 * @param t The Tokenizer to parse from
	 * @return The resulting Term
	 */
	public Term parseTerm(Tokenizer t){
		List<AbstractNode> termChildren = new LinkedList<AbstractNode>();
		termChildren.add(parseFactor(t));
		while(t.peek().isAddOp()){
			termChildren.add(parseAddOp(t));
			termChildren.add(parseFactor(t));
		}
		return new Term(termChildren);
	}

	/**
	 * Returns a Factor node representing a parse tree from the contents of a Tokenizer
	 * Requires: the next sequence of Tokens in t represents one Factor
	 * @param t The Tokenizer to parse from
	 * @return The resulting Factor
	 */
	public Factor parseFactor(Tokenizer t){
		List<AbstractNode> factorChildren = new LinkedList<AbstractNode>();
		factorChildren.add(parseAtom(t));
		while(t.peek().isMulOp()){
			factorChildren.add(parseMulOp(t));
			factorChildren.add(parseAtom(t));
		}
		return new Factor(factorChildren);
	}

	/**
	 * Returns a Atom node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Atom
	 * @param t The Tokenizer to parse from
	 * @return The resulting Atom
	 */
	public Atom parseAtom(Tokenizer t){
		Token tok = t.peek();
		if (!tok.isAtom()) {
			throw new InvalidSyntaxException("parseAtom can only be called on Atom tokens");
		}
		Atom rtn = null;
		Token consumed= null;
		switch (tok.getType()){
		case Token.NUM: rtn = new Atom(t.next().toNumToken().getValue()); break;
		case Token.MEM:
			t.next();
			consumed= t.next();
			if(consumed.getType() != Token.LBRACKET) {
				throw new InvalidSyntaxException("\"mem\" must be followed by \"[\"");
			}
			rtn = new Atom(Atom.MEM, parseTerm(t));
			consumed= t.next();
			if (consumed.getType() != Token.RBRACKET) {
				throw new InvalidSyntaxException("the Term in \"mem\" must be followed by \"]\"");
			}
			break;
		case Token.RANDOM:
			t.next();
			consumed= t.next();
			if(consumed.getType() != Token.LBRACKET) {
				throw new InvalidSyntaxException("\"random\" must be followed by \"[\"");
			}
			rtn = new Atom(Atom.RANDOM, parseTerm(t));
			consumed= t.next();
			if (consumed.getType() != Token.RBRACKET) {
				throw new InvalidSyntaxException("the Term in \"random\" must be followed by \"]\"");
			}
			break;
		case Token.LPAREN:
			t.next();
			rtn = new Atom(Atom.TERM, parseTerm(t));
			consumed= t.next();
			if (consumed.getType() != Token.RPAREN) {
				throw new InvalidSyntaxException("if Atom is a Term, it must be followed by a \")\"");
			}
			break;
		case Token.NEARBY: case Token.AHEAD: case Token.FOOD: case Token.DAMAGE://this Atom is a Sensor
			rtn = new Atom(parseSensor(t));
			break;
		default:
			System.out.println("parseAtom next token was" + tok.toString());
		}
		return rtn;		
	}

	/**
	 * Returns a Sensor node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next sequence of Tokens in t represents one Sensor
	 * @param t The Tokenizer to parse from
	 * @return The resulting Sensor
	 */
	public Sensor parseSensor(Tokenizer t){
		Token tok = t.next();
		if (!tok.isSensor()) {
			throw new InvalidSyntaxException("parseSensor can only be called on Sensor tokens");
		}
		Sensor rtn = null;
		Token consumed= null;
		switch (tok.getType()){
		case Token.DAMAGE: rtn = new Sensor(Sensor.DAMAGE); break;
		case Token.FOOD: rtn = new Sensor(Sensor.FOOD); break;
		case Token.NEARBY:
			consumed= t.next();
			if (consumed.getType() != Token.LBRACKET) {
				throw new InvalidSyntaxException("\"nearby\" must be followed by \"[\"");
			}
			rtn = new Sensor(Sensor.NEARBY, parseTerm(t));
			consumed= t.next();
			if (consumed.getType() != Token.RBRACKET) {
				throw new InvalidSyntaxException("the Term in \"nearby\" must be followed by \"]\"");
			}
			break;
		case Token.AHEAD:
			consumed= t.next();
			if (consumed.getType() != Token.LBRACKET) {
				throw new InvalidSyntaxException("\"ahead\" must be followed by \"[\"");
			}
			rtn = new Sensor(Sensor.AHEAD, parseTerm(t));
			consumed= t.next();
			if (consumed.getType() != Token.RBRACKET) {
				throw new InvalidSyntaxException("the Term in \"ahead\" must be followed by \"]\"");
			}
			break;
		}
		return rtn;		
	}

	/**
	 * Returns a AddOp node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next Token in t represents one AddOp
	 * @param t The Tokenizer to parse from
	 * @return The resulting AddOp
	 */
	public AddOp parseAddOp(Tokenizer t){
		Token tok = t.next();
		if (!tok.isAddOp()) {
			throw new InvalidSyntaxException("parseAddOp can only be called if the next Token is a AddOp token");
		}
		AddOp rtn = null;
		switch (tok.getType()){
		case Token.PLUS: rtn = new AddOp(AddOp.PLUS); break;
		case Token.MINUS: rtn = new AddOp(AddOp.MINUS); break;
		}
		return rtn;
	}

	/**
	 * Returns a MulOp node representing a parse tree from the contents of a Tokenizer
	 * Checks: the next Token in t represents one MulOp
	 * @param t The Tokenizer to parse from
	 * @return The resulting MulOp
	 */
	public MulOp parseMulOp(Tokenizer t){
		Token tok = t.next();
		if (!tok.isMulOp()) {
			throw new InvalidSyntaxException("parseMulOp can only be called if the next Token is a MulOp token");
		}
		MulOp rtn = null;
		switch (tok.getType()){
		case Token.MUL: rtn = new MulOp(MulOp.TIMES); break;
		case Token.DIV: rtn = new MulOp(MulOp.DIVIDE); break;
		case Token.MOD: rtn = new MulOp(MulOp.MOD); break;
		}
		return rtn;
	}
}
